const path = require('path'); // 引入path模块，用于处理文件路径 
const logger = require('morgan'); // 引入morgan模块，用于记录日志  
const express = require('express'); // 引入express模块，用于创建Express应用 
const createError = require('http-errors'); // 引入http-errors模块，用于创建HTTP错误  
const cookieParser = require('cookie-parser'); // 引入cookie-parser模块，用于解析Cookie  

// 控制台输出模块
const {
	consoleTest,
	socketShow
} = require('./routes/consoleTest');
// 用户在线列表
const {
	reconnectState,
	reconnect
} = require('./db/onlineUsers');
// socket可视化管理模块
const {
	instrument
} = require("@socket.io/admin-ui");

// 引入路由模块  
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const friendRouter = require('./routes/friend');
const emailRouter = require('./routes/email');
const testRouter = require('./routes/test');

// 从服务配置文件中获取相关服务器配置文件
const {
	app,
	io
} = require("./config/serverConfig.js");


// 从数据库模块中导入数据库
// 这里连接完成后，使用这里连接完成的mongoose对象，就可以使用数据库规则
const mgdb = require("./db/mgdb.js");


// 设置视图引擎为ejs，并指定视图文件所在的目录 
// 后面我们很少用到这个视图，因为前端部分由前端服务器显示
// 我们一般不会既用ejs(模板引擎)又用vue
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 使用morgan中间件记录日志 
// 这个参数是morgan模块预定义的一种日志格式，用于在控制台输出详细的调试信息。
// 当你使用'dev'作为参数时，morgan会记录每个HTTP请求的详细信息，包括请求方法、URL、状态码、响应时间和请求体等
// 这些信息对于开发过程中的调试非常有帮助。
app.use(logger('dev'));


// 使用express.urlencoded()中间件解析URL编码格式的请求体 
// 配置的是express.urlencoded()中间件函数的extended参数。这个参数是一个布尔值，用于指定是否使用qs库来解析URL编码的数据。
// 当extended设置为false时，会使用Node.js内置的querystring库来解析URL编码的请求体数据。这是一种较为简单和快速的解析方式，但可能不支持所有的URL编码特性。
// 如果extended设置为true，则会使用qs库来解析数据。qs库是一个更为强大和灵活的URL编码解析库，它支持更多的特性和复杂的URL编码格式，但可能会略微降低性能。
app.use(express.urlencoded({
	extended: false
}));

// 使用express框架自带的json方法，处理json类型的参数
app.use(express.json());

// 使用cookie-parser中间件解析Cookie  
app.use(cookieParser());

// 设置静态文件目录，用于提供静态资源  
app.use(express.static(path.join(__dirname, 'public')));

// 开启sokcet的可视化模块
instrument(io, {
	auth: false
});

// 导入sokcet路由
const game = require("./sockets/game.js");
const system = require("./sockets/system.js");
const groupChat = require("./sockets/groupChat.js");
const privateChat = require("./sockets/privateChat.js");
const {
	OnlineUser
} = require('./config/class');


// express的路由
// 工具类，使用测试路由，将测试相关信息显示在控制台
app.use(consoleTest);

// 使用路由中间件，处理根路径的请求  
app.use('/', indexRouter);

// 使用路由，处理/users路径的请求  
// 用户相关的请求
app.use('/users', usersRouter);

// 搜索好友相关的请求
app.use('/friend', friendRouter);

// 邮件相关的路由请求
app.use('/email', emailRouter);


// 测试路由中间件
app.use("/test", testRouter)


// sokcet连接事件
// sokcet是长连接，直到断开之前是不会结束任务的
// sokcet是实现qq这种即时通讯功能的框架，如果没有无法实现长连接
// 比如说，之前的纯express服务器要实现一个用户登出的功能，需要前端有个按钮，点击以后登出
// 或者使用心跳包实现，心跳包就是客户端，每隔若干秒，向后端get请求一次，服务器就知道客户端还未离线，是在线状态
// 但是如果用的sokcet，进行了长连接，当用户断开时，服务器能够检测到用户断开 
const onConnection = (socket) => {
	// socket请求，连接一次以后，只有当用户重新连接的时候才会触发
	// 而socket是一个长连接，一般不会断开
	const req = socket.request;
	// 这里的session，和http中的session是一样的
	// 这里，如果用户登录了的话，session中的user值是存在的
	// 可以通过这个值，判断用户是否已经登录
	const session = socket.request.session;

	// 如果用户已经登录,需要判断是否为重连
	// 当用户在规定时间内重新连接，就需要启动重连方法
	if (session.user) {
		reconnect(session.user._id)
	}

	// 手动更新session信息
	// 由于session不绑定到单个 HTTP 请求，因此必须手动重新加载并保存会话：
	socket.use((__, next) => {
		// 重新加载session
		req.session.reload((err) => {
			if (err) {
				socket.disconnect();
			} else {
				next();
			}
		});
	});

	// 接收用户的所有类型发送
	// 将socket接收的数据，输出到控制台上
	socket.onAny((event, sendBody) => {
		socketShow(event, sendBody);
	});

	// 这里相当于是socket的路由
	// 游戏事件
	game(io, socket);
	// 系统事件
	system(io, socket);
	// 群聊事件
	groupChat(io, socket);
	// 私聊事件
	privateChat(io, socket);

	// 当客户端断开连接的时候触发该事件
	// socketio的事件触发，跟前端的事件很像
	// 比如这个例子中，socket是当前和服务器连接对象，每隔用户一个
	// 我们通过监听socket的触发事件disconnect，来检测用户是否在线
	socket.on('disconnect', () => {
		// socket在未登录情况下也能连接，所以需要判断是否是登录以后断开
		if (socket.request.session.user) {
			console.log("用户" + socket.request.session.user.username + "断开socket");
			// 添加该用户的重连状态
			// 断开连接后，我们需要设置一个延时，当倒计时完成，清除用户在线的痕迹
			reconnectState(socket.request.session.user, () => {
				// 销毁保存的session内容
				// 任何登录状态的保存都是以session为标准，所以登出时需要删除session状态
				socket.request.session.destroy((err) => {
					if (err) {
						return console.error(err);
					}
				});
			});
		} else {
			//一个临时用户断开连接
			console.log("游客用户断开连接");
		}
	});
}
// 每当有用户连接的时候，会触发这个连接时间
io.on("connection", onConnection);

// 捕获404错误，并转发给错误处理中间件  
app.use(function(req, res, next) {
	next(createError(404));
});

// 错误处理中间件  
app.use(function(err, req, res, next) {
	// 设置局部变量，只在开发环境下提供错误信息  
	res.locals.message = err.message;
	res.locals.error = req.app.get('env') === 'development' ? err : {};

	// 渲染错误页面，并设置状态码  
	res.status(err.status || 500);
	res.render('error');
});

// 导出Express应用，以便在其他文件中使用  
module.exports = {};